package sim.test;

import camera.AbstractCamera;
import camera.IPCam;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import sim.camera.SimCam;
import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Line;
import com.jme3.texture.FrameBuffer;
import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture2D;
import com.jme3.util.BufferUtils;
import com.jme3.util.Screenshots;
import environment.AbstractEnvironment;
import environment.Environment2;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.geom.Area;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import javax.imageio.ImageIO;
import javax.management.modelmbean.ModelMBean;
import javax.media.Buffer;
import javax.swing.Timer;
import jme3tools.navigation.MapModel3D;
import jme3tools.navigation.Position;
import sim.system.SystemSetup;
import sim.env.Environment;
import sim.env.Obstacle;
import sim.env.ObstacleGeometry;
import sim.models.BaseModel;
import sim.models.SinbadModel;
import trackingAlgorithms.FrameProcessor;
import trackingAlgorithms.blob.Blob;

/**
 * Test multiple viewports.
 * 
 * @author Ahmed Abdelkader
 */
public class Simulator1 extends SimpleApplication implements java.awt.event.ActionListener {

    private final Vector3f X = Vector3f.UNIT_X;
    private final Vector3f Y = Vector3f.UNIT_Y;
    private final Vector3f Z = Vector3f.UNIT_Z;
    private BulletAppState bulletAppState;
    private ArrayList<BaseModel> models = new ArrayList<BaseModel>();
    private ArrayList<AbstractCamera> cameras = new ArrayList<AbstractCamera>();
    private ArrayList<AbstractCamera> camerasInImage = new ArrayList<AbstractCamera>();
    private ArrayList<Obstacle> obstacles = new ArrayList<Obstacle>();
    private ArrayList<Vector3f> modelspos = new ArrayList<Vector3f>();
    private ArrayList<Blob> blobs = new ArrayList<Blob>();
    private ArrayList<ObstacleGeometry> ObstaclesPositions = new ArrayList<ObstacleGeometry>();
    private Node bodies;
    private Environment environment;
    private SystemSetup sys;
    private BufferedImage buim;
    private ByteBuffer cpubuf;
    private FrameBuffer buf;
    private ViewPort port;
    private int num = 0;
    private AbstractEnvironment env;
    private FrameProcessor frameProcessor;
    private boolean takeFrame;
    private int mapWidth = 900;
    private int mapHeight = 900;
    private int screenWidth = 640;
    private int screenHeight = 480;
    private Timer timer;
    private Vector3f inModels[];
    private ActionListener actionListener = new ActionListener() {

        public void onAction(String name, boolean pressed, float tpf) {
            System.out.println(renderManager.removeMainView("Top Left"));
        }
    };

    private PhysicsSpace getPhysicsSpace() {
        return bulletAppState.getPhysicsSpace();
    }

    //TODO read the setup from file.
    private void init() {
        try {
            // Open the file that is the first 
            // command line parameter
            FileInputStream fstream = new FileInputStream("setup.txt");
            // Get the object of DataInputStream
            DataInputStream in = new DataInputStream(fstream);
            BufferedReader br = new BufferedReader(new InputStreamReader(in));
            String strLine = br.readLine(); // the first line the map width and height
            String[] tokens = strLine.split(" ");
            mapWidth = Integer.parseInt(tokens[0]);
            mapHeight = Integer.parseInt(tokens[1]);

            strLine = br.readLine(); // the first line the map width and height
            tokens = strLine.split(" ");
            screenWidth = Integer.parseInt(tokens[0]);
            screenHeight = Integer.parseInt(tokens[1]);

            strLine = br.readLine(); // the third line number of moving subjects
            tokens = strLine.split(" ");
            int numPoints = Integer.parseInt(tokens[0]);
            int[] xS = new int[numPoints];
            int[] yS = new int[numPoints];
            strLine = br.readLine(); // the third line number of corners in the circumference
            tokens = strLine.split(" ");
            // get the x points
            for (int i = 0; i < tokens.length; i++) {
                xS[i] = Integer.parseInt(tokens[i]);
            }
            strLine = br.readLine(); // the third line number of moving subjects
            tokens = strLine.split(" ");
            // get the y points
            for (int i = 0; i < tokens.length; i++) {
                yS[i] = Integer.parseInt(tokens[i]);
            }
            Polygon outer = new Polygon(xS, yS, numPoints);
            env = new Environment2(outer);

            strLine = br.readLine(); // the sixth line number of moving subjects
            tokens = strLine.split(" ");
            int numberOfModels = Integer.parseInt(tokens[0]);
            inModels = new Vector3f[numberOfModels];
            for (int i = 0; i < numberOfModels; i++) {
                strLine = br.readLine(); // read model initial position.
                tokens = strLine.split(" ");
                inModels[i] = new Vector3f(Integer.parseInt(tokens[0]), Integer.parseInt(tokens[1]), Integer.parseInt(tokens[2]));
                models.add(sys.addModel(new SinbadModel(this)));
                models.get(i).setLocalTranslation(inModels[i].x, inModels[i].y, inModels[i].z);
            }

            strLine = br.readLine(); // Read in number of obstacles.
            tokens = strLine.split(" ");
            int numberOfobstacles = Integer.parseInt(tokens[0]);
            for (int i = 1; i <= numberOfobstacles; i++) {
                strLine = br.readLine(); // read obstacle properties
                tokens = strLine.split(" ");
                Vector3f obstacleInPixels = new Vector3f(Integer.parseInt(tokens[0]), Integer.parseInt(tokens[5]), (Integer.parseInt(tokens[2]))); // get obstacle values in pixels.
                obstacleInPixels = convertToPixels(obstacleInPixels);
                int wallWidth = (screenWidth / 2 - (int) obstacleInPixels.x) * 2;
                int wallHeight = (screenHeight / 2 - (int) obstacleInPixels.z) * 2;
                obstacleInPixels.setX(Integer.parseInt(tokens[0]) - Integer.parseInt(tokens[4]));
                obstacleInPixels.setZ(Integer.parseInt(tokens[2]) + Integer.parseInt(tokens[6]));
                obstacleInPixels = convertToPixels(obstacleInPixels);
                int strtX = (int) obstacleInPixels.x;
                int strtY = (int) obstacleInPixels.z;
                ObstaclesPositions.add(new ObstacleGeometry(strtX, strtY, strtX + wallWidth, strtY + wallHeight, wallWidth, wallHeight));
                environment.createObstacle(Integer.parseInt(tokens[0]), Integer.parseInt(tokens[1]), Integer.parseInt(tokens[2]), tokens[3]);
                environment.getObstacles().get(i).setLocalTranslation(Integer.parseInt(tokens[4]), Integer.parseInt(tokens[5]), Integer.parseInt(tokens[6]));

            }
            // add obstacles to the environment
            for (int i = 0; i < ObstaclesPositions.size(); i++) {
                env.addObstacle(ObstaclesPositions.get(i).getObstacleRectangle());
            }

            env.getFinalArea(); // get the final area after cutting the obstacles
            obstacles = environment.getObstacles();

            strLine = br.readLine(); // Read in master camera videport
            tokens = strLine.split(" ");

            cam.setLocation(new Vector3f(0f, 1800f, 0f));
            cam.lookAt(Y.negate(), Z);
            cam.setFrustumFar(2400f);
            renderManager.createMainView("main", cam.clone());
            screenWidth = cam.getWidth();
            screenHeight = cam.getHeight();

            float masterPortValues[] = {Float.parseFloat(tokens[0]), Float.parseFloat(tokens[1]), Float.parseFloat(tokens[2]), Float.parseFloat(tokens[3])};
            cam.setViewPort(masterPortValues[0], masterPortValues[1], masterPortValues[2], masterPortValues[3]);

            strLine = br.readLine(); // Read in number of cameras
            tokens = strLine.split(" ");
            int numberOfCameras = Integer.parseInt(tokens[0]);
            int r = 400;
            for (int i = 0; i < numberOfCameras; i++) {
                strLine = br.readLine(); // Read slave camera properties
                tokens = strLine.split(" ");
                AbstractCamera slaveCam = new SimCam(cam.clone());
                float viewPortValues[] = {Float.parseFloat(tokens[0]), Float.parseFloat(tokens[1]), Float.parseFloat(tokens[2]), Float.parseFloat(tokens[3])};
                Vector3f location = new Vector3f(Float.parseFloat(tokens[4]), Float.parseFloat(tokens[5]), Float.parseFloat(tokens[6]));
                float angle = Float.parseFloat(tokens[7]);
                float dirX = FastMath.abs(r * FastMath.cos((float) Math.toRadians(angle)));
                float dirY = FastMath.abs(r * FastMath.sin((float) Math.toRadians(angle)));

                if (Float.compare(dirX, dirY) >= 0) {
                    dirY = dirY / dirX;
                    dirX = dirX / dirX;
                } else {
                    dirX = dirX / dirY;
                    dirY = dirY / dirY;
                }
                if (Float.compare(angle, 90) <= 0) {
                    if (Float.compare(dirX, 0) >= 0) {
                        dirX = -dirX;
                    }
                    if (Float.compare(dirY, 0) <= 0) {
                        dirY = -dirY;
                    }
                } else if (Float.compare(angle, 180) <= 0) {
                    if (Float.compare(dirX, 0) <= 0) {
                        dirX = -dirX;
                    }
                    if (Float.compare(dirY, 0) <= 0) {
                        dirY = -dirY;
                    }
                } else if (Float.compare(angle, 270) <= 0) {
                    if (Float.compare(dirX, 0) <= 0) {
                        dirX = -dirX;
                    }
                    if (Float.compare(dirY, 0) >= 0) {
                        dirY = -dirY;
                    }
                } else {
                    if (Float.compare(dirX, 0) >= 0) {
                        dirX = -dirX;
                    }
                    if (Float.compare(dirY, 0) >= 0) {
                        dirY = -dirY;
                    }
                }
                Vector3f dir = new Vector3f(dirX, 0, dirY);
                slaveCam.setViewPort(viewPortValues);
                sys.AddCamera(slaveCam, location, dir, tokens[8], renderManager, true, Boolean.parseBoolean(tokens[9]));
                Vector3f loc = convertToPixels(location);
                dirX = (float) (r * Math.cos(Math.toRadians(angle)) + loc.x);
                dirY = (float) (r * Math.sin(Math.toRadians(angle)));
                dirY = loc.z - dirY;
                Vector3f dirIP = new Vector3f(dirX, 0f, dirY);
                camerasInImage.add(new IPCam(loc, dirIP, null, null, screenWidth));
            }
            in.close();
        } catch (Exception e) {//Catch exception if any
            System.err.println("Error: " + e.getMessage());
        }

    }

    @Override
    public void simpleInitApp() {

        bulletAppState = new BulletAppState();
        bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL);
        stateManager.attach(bulletAppState);
        timer = new Timer(200, this);
        bodies = new Node("Bodies");
        takeFrame = false;
        environment = new Environment(getPhysicsSpace(), bodies, this, mapWidth, mapHeight);
        sys = new SystemSetup(getPhysicsSpace(), cameras, rootNode, bodies, mapWidth, mapHeight);

        sys.AddDirectLight(ColorRGBA.White, Y);
        sys.AddDirectLight(ColorRGBA.White, Y.negate());
        /* Setup models */
        init(); // initialization function reads system properties from a file

        Node walls = new Node("Obstacles");
        for (Obstacle wall : obstacles) {
            walls.attachChild(wall.getSpatial());
        }
        rootNode.attachChild(walls);

        /* Setup input mapping */
        inputManager.addMapping("ToggleLights", new KeyTrigger(KeyInput.KEY_F));
        inputManager.addListener(actionListener, "ToggleLights");
        viewPort.setBackgroundColor(ColorRGBA.Black);



        /* setting view port used for capturing images*/
        port = renderManager.getMainView("main");
        int w = port.getCamera().getWidth();
        int h = port.getCamera().getHeight();
        buf = new FrameBuffer(w, h, 0);
        Texture2D tex = new Texture2D(w, h, Format.RGBA8);
        buf.setColorTexture(tex);
        port.setOutputFrameBuffer(buf);
        cpubuf = BufferUtils.createByteBuffer(w * h * 4);
        buim = new BufferedImage(w, h, BufferedImage.TYPE_4BYTE_ABGR);
        port.detachScene(rootNode);
        port.attachScene(walls);
        frameProcessor = new FrameProcessor(env, camerasInImage, null, null);
        port.attachScene(bodies);
        timer.start();

    }
    int ccc = 0;
    Geometry[] vis = new Geometry[40];

    @Override
    public void simpleUpdate(float tpf) {
        try {
            num++;
            int j = 0;
            modelspos = new ArrayList<Vector3f>();
            blobs = new ArrayList<Blob>();

            //        for (BaseModel model : models) {
            //            model.move();
            //        }

            bulletAppState.update(tpf);

            /*setting the output bufer and converting it to bufferedImage */
            buf = port.getOutputFrameBuffer();
            renderManager.getRenderer().readFrameBuffer(buf, cpubuf);
            Screenshots.convertScreenShot(cpubuf, buim);
            Graphics2D _g = (Graphics2D) buim.getGraphics();
            _g.setColor(Color.red);
            for (Area wall : env.getObstacles()) {
                _g.draw(wall);
            }
            ImageIO.write(buim, "png", new File("TestConversion\\test_" + num + ".png"));


            //        Buffer b = new Buffer();
            //        b.setData(buim.getData());
            //        Buffer v = new Buffer();
            //        b.copy(v);
            //        if (takeFrame) {
            //            takeFrame = false;
            //            frameProcessor.processFrame(buim,buim);
            //
            //            blobs = frameProcessor.blobs;
            //            modelspos = calculatePositions(blobs);
            //            for (int i = 0; i < vis.length; i++) {
            //                if (vis[i] != null) {
            //                    vis[i].removeFromParent();
            //                }
            //            }
            //
            //            /*
            //             *Draw red cross in the center of the detected blob
            //             */
            //
            //            for (Vector3f model : modelspos) {
            //
            //                Vector3f vect1 = new Vector3f(model);
            //                vect1.setX(vect1.getX() - 20);
            //                Vector3f vect2 = new Vector3f(model);
            //                vect2.setX(vect2.getX() + 20);
            //                Vector3f vect3 = new Vector3f(model);
            //                vect3.setZ(vect3.getZ() - 20);
            //                Vector3f vect4 = new Vector3f(model);
            //                vect4.setZ(vect4.getZ() + 20);
            //
            //                rootNode.attachChild(vis[j] = makeLine("", vect1, vect2, ColorRGBA.Red));
            //                j++;
            //                rootNode.attachChild(vis[j] = makeLine("", vect3, vect4, ColorRGBA.Red));
            //                j++;
            //            }
            //
            //            /*
            //             *Draw blue cross in the center of real model center
            //             */
            //            for (BaseModel model : models) {
            //                Vector3f vect1 = new Vector3f(model.getLocalTranslation());
            //                vect1.setX(vect1.getX() - 50);
            //                Vector3f vect2 = new Vector3f(model.getLocalTranslation());
            //                vect2.setX(vect2.getX() + 20);
            //                Vector3f vect3 = new Vector3f(model.getLocalTranslation());
            //                vect3.setZ(vect3.getZ() - 20);
            //                Vector3f vect4 = new Vector3f(model.getLocalTranslation());
            //                vect4.setZ(vect4.getZ() + 20);
            //
            //                rootNode.attachChild(vis[j] = makeLine("", vect1, vect2, ColorRGBA.Blue));
            //                j++;
            //                rootNode.attachChild(vis[j] = makeLine("", vect3, vect4, ColorRGBA.Blue));
            //                j++;
            //            }
            //            /*
            //             *Draw red line between the camera and the assigned moving subject if it is not occluded
            //             * else
            //             * draw a blue line.
            //             */
            //            try {
            //                if (frameProcessor.tracker.tUpdator.to.size() > 0) {
            //                    for (int i = 0; i < frameProcessor.tracker.tUpdator.from.size(); i++) {
            //                        Point from = new Point((int) frameProcessor.tracker.tUpdator.from.get(i).x, (int) frameProcessor.tracker.tUpdator.from.get(i).z);
            //                        Point to = new Point((int) frameProcessor.tracker.tUpdator.to.get(i).x, (int) frameProcessor.tracker.tUpdator.to.get(i).z);
            //                        int index = frameProcessor.getIndexOf(from);
            //                        boolean cs = frameProcessor.tracker.tUpdator.isInsideVisibilityPolygon(null, new Vector3f(to.x, 0, to.y), index);
            //                        Vector3f fromvect = cameras.get(index).getLocation();
            //                        Vector3f tovect = new Vector3f(modelspos.get(frameProcessor.tracker.tUpdator.matching[index]));
            //                        fromvect.setY(150);
            //                        cameras.get(index).updateCamera(tovect);
            //                        rootNode.attachChild(vis[j] = makeLine("", fromvect, tovect, cs ? ColorRGBA.Red : ColorRGBA.Blue));
            //                        j++;
            //
            //                    }
            //                }
            //            } catch (NullPointerException e) {
            //            }
            //        }
        } catch (IOException ex) {
            Logger.getLogger(Simulator1.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public Geometry makeLine(String name, Vector3f s, Vector3f e, ColorRGBA c) {
        Line line = new Line(s, e);
        Geometry geom = new Geometry(name, line);
        Material mat1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat1.setColor("m_Color", c);
        geom.setMaterial(mat1);
        return geom;
    }

    /**
     * calculates positions of the detected blobs in world units.
     * @return {@link ArrayList<Vector3f>} of positons
     */
    public ArrayList<Vector3f> calculatePositions(ArrayList<Blob> blobs) {
        modelspos = new ArrayList<Vector3f>();

        for (int i = 0; i < blobs.size(); i++) {
            Blob blob = blobs.get(i);
            float xx, zz;
            xx = (float) ((320f - blob.bounds.getCenterX()) * 2.811594203);
            zz = (float) ((240f - (480 - blob.bounds.getCenterY())) * 3.028821839);
            Vector3f vect = new Vector3f(xx, 150f, zz);
            modelspos.add(vect);
        }
        return modelspos;
    }

    private Vector3f convertToWorld(Vector3f vec) {
        float xx, zz;
        xx = (float) ((320f - vec.x) * 2.811594203);
        zz = (float) ((240f - (480 - vec.z)) * 3.028821839);
        zz = -zz;
        Vector3f vect = new Vector3f(xx, 150f, zz);
        System.out.println(vect.toString());
        return vect;
    }

    private void convertToLongLat(Vector3f vec) {
        MapModel3D m = new MapModel3D(800);
        m.setWorldHeight(1200);
        Position pp = m.toPosition(vec);
        System.out.println("vec: " + " " + vec.x + ", " + vec.y + ", " + vec.z + " = " + pp.getLongitude() + " " + pp.getLatitude());
    }

    /**
    //     * convert from world units to pixels
    //     * @param vec
    //     *             Vector3f of points in world units
    //     *             
    //     * @return 
     *              Vector3f of points in pixels
    //     */
    private Vector3f convertToPixels(Vector3f vec) {
        int x = (int) (320 - vec.x * 0.3556701031);
        int y = (int) (240 - vec.z * 0.3301613806);
        return new Vector3f(x, 0, y);
    }

    @Override
    public void simpleRender(RenderManager rm) {
        //TODO: add render code
    }

    public static void main(String[] args) {
        Simulator1 app = new Simulator1();
        app.start();
    }

    public void actionPerformed(ActionEvent e) {
        if (e.getSource() == timer) {
            takeFrame = true;
        }
    }
}
